package server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Enumeration;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
 * Clase que encapsula la creaci�n de los sockets SSL que ser�n utilizados en el servidor del sistema.
 * @author Matias
 *
 */
public class ServiciosSSL {
	private SSLContext ssl_context;
	private SSLServerSocketFactory ssl_factory;
	
	//private static final String[] PROTOCOLOS_ADMITIDOS = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2" };
	private static final String[] PROTOCOLOS_ADMITIDOS = new String[]{"TLSv1.2" };
	//private static final String[] CIPHER_SUITES_SOPORTADOS = new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_CBC_SHA"};
	
	// --- variables del KS ---
	//private char[] ks_pass = "123456".toCharArray();
	//private char[] keys_pass ="123456".toCharArray(); // "abcdefg".toCharArray()
	//private char[] keys_pass ="abcdefg".toCharArray(); // "abcdefg".toCharArray()
	//private String ks_path= "Nservidor.jks"; // "servidor.jks"
	private final static String KS_PATH= "keystore/servidor.jks";
	private Log log;
	
	/**
	 * Constructor de la clase por default (no s� que pensaba hacer ac�)
	 */
	public ServiciosSSL(Log l){
		Security.addProvider(new BouncyCastleProvider());
		log=l;
		
	}
	/**
	 * M�todo que hace un load de la clave privada y certificados p�blicos almacenados en el jks, y crea un sever factory
	 * a partir del cual es posible crear servidores SSL en base a la configuraci�n establecida [ver como especificar configuraci�n
	 * de algoritmos].
	 * @param pass_ks : password del archivo jks
	 * @param pass_keys : password de la claves almacenadas en el jks
	 */
	public void iniciar_keystores(char[] pass_ks, char[] pass_keys){
		
		try {
			KeyStore key_store = KeyStore.getInstance("JKS"); // obtengo una instancia de keystore del tipo JKS (est�ndar de java)
			InputStream keystoreStream;
			if(new File("keystore/servidor.jks").isFile()){
				keystoreStream = new FileInputStream("keystore/servidor.jks");
				System.out.println("usando el nuevo");
			}else{
				keystoreStream  = getClass().getClassLoader().getResourceAsStream(KS_PATH);
			}
			//InputStream keystoreStream = getClass().getClassLoader().getResourceAsStream("servidor.jks");
			
			key_store.load(keystoreStream, pass_ks);
			
			KeyManagerFactory key_manager = KeyManagerFactory.getInstance("SunX509"); // obtengo instancia para gestionar los certificados del tipo SunX509
			//key_manager.init(key_store, "abcdefg".toCharArray());* // clave para las claves (p�blica/privada) del servidor
			key_manager.init(key_store, pass_keys); // clave para las claves (p�blica/privada) del servidor
			
			TrustManagerFactory trust_store = TrustManagerFactory.getInstance("SunX509");
			trust_store.init(key_store);
			         
			ssl_context = SSLContext.getInstance("SSL");
			ssl_context.init(key_manager.getKeyManagers(), trust_store.getTrustManagers(), null);
			
			         
			ssl_factory = ssl_context.getServerSocketFactory();
			
			
			
			} catch (Exception e) {
				System.err.println("NO SE PUDO INICIAR EL KEYSTORE DEL SERVIDOR: "+e.toString());
				//e.printStackTrace();
			}
	}
	
	/**
	 * M�todo que crea un servidor ssl (escuchando en el puerto que recibe como par�metro) y lo devuelve. El servidor es creado utilizando
	 * el SSLServerFactory iniciado en el m�todo "iniciar_keystore", esto implica que de no haber invocado previamente al m�todo  mencionado, se
	 * producir� un error. 
	 * @param puerto : entero con nro de puerto
	 * @return servidor ssl escuchando en el puerto "puerto"
	 */
	public SSLServerSocket generarServidor(int puerto){
		StringBuffer sb = new StringBuffer();
		SSLServerSocket servidor=null;
		try {
			servidor = (SSLServerSocket) ssl_factory.createServerSocket(puerto);
			servidor.setNeedClientAuth(true); // establece que el servidor requiere autentificaci�n del cliente
			servidor.setEnabledProtocols(PROTOCOLOS_ADMITIDOS);
			//servidor.setEnabledCipherSuites(CIPHER_SUITES_SOPORTADOS);
		} catch (IOException e) {
			System.err.println(e.toString());
			e.printStackTrace();
		}
		sb.append("\n======================================================================");
		sb.append("\n         CREACION DE SERVIDOR ESCUCHANDO POR PUERTO: "+puerto);
		sb.append("\n======================================================================");		
		sb.append(printServerSocketInfo(servidor));
		sb.append("\n----------------------------------------------------------------------");
		//System.out.println(sb.toString());
		log.writeLog(Log.INFO, "SERVIDOR", "Estado 0", sb.toString());
		return servidor;
	}
	
	/**
	 * M�todo que imprime informaci�n del socket SSL que recibe como par�metro.
	 * @param s : socket SSL
	 * @throws SSLPeerUnverifiedException
	 */
	public String printSocketInfo(SSLSocket s) throws SSLPeerUnverifiedException {
		StringBuffer sb = new StringBuffer();
		
		sb.append("\n______________________________________________________________________");
		sb.append("\n                    NUEVA CONEXIÓN ENTRANTE");
		sb.append("\n----------------------------------------------------------------------");
		sb.append("\nClase del Socket: "+s.getClass());
		sb.append("\n   IP Remota = "+s.getInetAddress().toString());
		sb.append("\n   Puerto Remoto = "+s.getPort());
		//sb.append("\n   IP Local = "+s.getLocalSocketAddress().toString());
		sb.append("\n   IP Local = "+s.getLocalAddress().toString());
		sb.append("\n   Puerto Local = "+s.getLocalPort());
		sb.append("\n   Autenticación del Cliente Habilitado = "+s.getNeedClientAuth());
		
		
		SSLSession ss = s.getSession();
		
		sb.append("\nClase de la Sesión: "+ss.getClass());
		sb.append("\n   Protocolo Utilizado= "+ss.getProtocol());
		sb.append("\n   Cipher Suite Utilizado= "+ss.getCipherSuite());
		
		
		sb.append("\n   Datos del Cliente Remoto = "+ss.getPeerPrincipal().getName());
		sb.append("\n   Datos del Servidor Local = "+ss.getLocalPrincipal().getName());
			//System.out.println("   Nombre del cliente: "+obtenerCommonName(s));
		sb.append("\n______________________________________________________________________");
		//System.out.println(sb.toString());
		log.writeLog(Log.INFO, "SERVIDOR", "Estado 0", sb.toString());
		return sb.toString();
	}
	
	/**
	 * M�todo que imprime informaci�n del server socket recibidomo como par�metro.
	 * @param s : server socket
	 */
	public String printServerSocketInfo(SSLServerSocket s) {
		StringBuffer sb= new StringBuffer();
		sb.append("\nServer socket class: "+s.getClass());
		sb.append("\n   IP del Socket: "+s.getInetAddress().toString());
		sb.append("\n   Puerto del Socket = "+s.getLocalPort());
		sb.append("\n   Autenticacion del cliente habilitado : "+s.getNeedClientAuth());
		//sb.append("\n   Want client authentication : "+s.getWantClientAuth());
		sb.append("\n   Usar modo del cliente: "+s.getUseClientMode());
		String[] cipherSuites = s.getEnabledCipherSuites();
		String[] protocolos = s.getEnabledProtocols();
		sb.append("\nCipher suites soportados:");
		for(int i=0; i<protocolos.length; i++){
			sb.append("\n\t. "+cipherSuites[i]);
		}
		sb.append("\nProtocolos soportados:");
		for(int i=0; i<protocolos.length; i++){
			sb.append("\n\t. "+protocolos[i]);
		}
		return sb.toString();
	} 
	public static void cambiarPassKS(char[] passViejo, char[] passNuevo){
		Security.addProvider(new BouncyCastleProvider());
		 String dir="keystore";
		 //String path="servidor.jks";
		 String path_nuevo="keystore/servidor.jks";
		 try { 
		 InputStream ksStream;
			if(new File(path_nuevo).isFile()){
				System.out.println("LO ENCONTRO");
				ksStream = new FileInputStream("keystore/servidor.jks");
			}else{
				System.out.println("NOOOO LO ENCONTRO");
				  ksStream  = ServiciosSSL.class.getClassLoader().getResourceAsStream(KS_PATH);
				  new File(dir).mkdir();
			}
		 /*
		 if(new File(path_nuevo).isFile()){
				System.out.println("El archivo YA existe!");
				path=path_nuevo;
				//passViejo=passNuevo;
		 }else{
			 new File(dir).mkdir();
			 System.out.println("El archivo NO existe!");
		 }
		 */
		
			KeyStore ks_viejo = KeyStore.getInstance("JKS");
			//ksStream = new FileInputStream(path);
			ks_viejo.load(ksStream, passViejo);
	        ksStream.close();
			
	        KeyStore ks_nuevo = KeyStore.getInstance("JKS");
	        ks_nuevo.load(null);
	        
	        Enumeration<String> aliases=ks_viejo.aliases();
			while(aliases.hasMoreElements()){
				String alias=aliases.nextElement();
				System.out.println("#"+alias);
				if(ks_viejo.isKeyEntry(alias)){ // si es clave privada
					char[] passKeyViejo= (passViejo.length>6)? Arrays.copyOfRange(passViejo, 0, 7) : passViejo;
					char[] passKeyNuevo= (passNuevo.length>6)? Arrays.copyOfRange(passNuevo, 0, 7) : passNuevo;
					ks_nuevo.setKeyEntry(alias, ks_viejo.getKey(alias, passKeyViejo), passKeyNuevo, new Certificate[]{ks_viejo.getCertificate(alias)});
				}else{// es certificado
					ks_nuevo.setCertificateEntry(alias, ks_viejo.getCertificate(alias));
				}
				
			}
	        
			File ks_file2 = new File(path_nuevo);
			
			FileOutputStream stream2 = new FileOutputStream(ks_file2,false);// false para sobreescribir
			ks_nuevo.store(stream2, passNuevo);
			ksStream.close();
			System.out.println("#se cambio el password del keystore correctamente");
			
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		}
		
	}
	public static boolean verificarPassKS(char[] pass){
		KeyStore ks;
		InputStream ksStream;
		try {
			ks = KeyStore.getInstance("JKS");
			if(new File("keystore/servidor.jks").isFile()){
				ksStream = new FileInputStream("keystore/servidor.jks");
			}else{
				  ksStream  = ServiciosSSL.class.getClassLoader().getResourceAsStream(KS_PATH);
			}
			ks.load(ksStream, pass);
	        ksStream.close();
	        return true;
		} catch (KeyStoreException e) {
			System.err.println("No se pudo crear instancia de JKS: "+e.getMessage());
			return false;
		} catch (FileNotFoundException e) {
			System.err.println("No se encontro el archivo con el keystore "+e.getMessage());
			return false;
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} catch (CertificateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} catch (IOException e) {// <<< por acá sale el password incorrecto
			e.printStackTrace();
			return false;
		}
		
		
	}


}
